Vapor Part 1 - File Structure
This is the first part in a series about the open source Server-Side Swift web framework Vapor. My goal here is to condense my knowledge and experiences with it into chunks that can get you up and running with your own Vapor app quickly.
In this post I'm going to talk about the overall structure of a Vapor app. It's very different than you might be used to if you've only ever stayed within the Cocoa ecosystem of apps in your Swift development journey.
I'm not sponsored by the Ray Wenderlich team, but their book called Server Side Swift with Vapor was the thing that helped me learn Vapor initially, and is still a great resource. If you want more in-depth knowledge about various parts of Vapor I'd highly recommend checking it out.
Set-up
First off, if you want to follow along and don't have Vapor on your machine, go to this page in Vapor's documentation for instructions on how to install it. If you don't want to follow along, that's great too! For this project I'll be using Vapor 3.0 as Vapor 4 is still in beta at the time of writing.
When creating a new Vapor project, you choose a template from which to start from. There are various built-in templates you can use similar to how Xcode has templates for iOS/macOS apps, and so on. The default template when you create a Vapor project is for a making a JSON API. Alternatively, you can point to a template that someone made and hosts on GitHub. I personally like Paul Hudson's "Vapor Clean" template as it's a truly barebones template. The default JSON API template is nice, but it comes with some models and routing code that you would remove unless you plan on making a to-do app. These are nice as a reference, but I'd like to start clean. More on templates here.
- To create a new Vapor project, navigate to the directory you want it to be stored at using Terminal (or your CLI of choice) and enter vapor new <ProjectNameHere> --template=twostraws/vapor-clean
. Like I said previously, if you don't specify the --template
argument and give it a value, it will default to using the built-in JSON API template.
- You can write your Vapor app in any code editor but I'm used to Xcode, so in the terminal navigate to the new folder with the project name from the previous command and enter
vapor xcode
, and an Xcode project file will be generated for you. Vapor will if you want to open the xcode project or not. Just entery
and it will open it. You can also entervapor xcode -y
to perform both commands at once.
A Whole New World
(For the purposes of this post, I'll assume that you're looking at a project that uses the vapor-clean template which is what this image is showing)
As soon as you open the Xcode project, you'll notice that it looks very different than a normal Cocoa application project. Let's go through each part and make sense of it.
Package.swift
This isn't a file specific to Vapor but to the Swift Package Manager. Here we define the dependencies that we want the Swift Package Manager to clone (and add to the Xcode project the next time it is generated). In the dependencies
argument to the initializer, we add packages. The default includes Vapor and depending on which template you used to create this project, you could have more packages in here. In the targets
argument, the name of those package(s) ("Vapor" for example) are added to the dependencies
argument in the "App" target. This links them so you can import and use those dependencies in your code. This is great because thanks to the Swift Package Manager, there is no need to rely on Carthage and you integrating these dependencies yourself, or letting CocoaPods do the work and inevitably break something. 🎉
"App" Group
Inside of the "App" group is where all of your code files to make the app functional will live. You'll see that there are already three files in this by default:
boot.swift
This has a single function called public func boot(_ app: Application) throws
. It's simply a function to allow you do run any code before the actual application starts. You could think of it as being similar to an iOS application's AppDelegate
didFinishLaunchingWithOptions
method; it's about the first place you can run your own code when the app starts.
configure.swift
configure.swift holds an important function. The configure
function is where we can set up different parts of our app. For example, if we want to use a database of some sort, we need to "register" it so it can perform any necessary setup it needs to. Things of this nature are called "Services". Services conform to a protocol called Provider
, which allow external services to be integrated into your application. Vapor itself is sort of broken up into smaller parts. The Vapor GitHub organization has quite a few repositories that would be considered "external services". And it makes sense to do so; we can keep our applications smaller by only including the frameworks we care about. Not using a database? Don't include it. If you are, you just have to add it to the Package.swift as a dependency, then register it here. We'll get into how to register services when we get into the code.
There are also things called Middlewares. From Vapor's documentation they are described as such:
Middleware is a logic chain between the client and a Vapor route handler. It allows you to make operations on incoming requests before they get to the route handler, and on outgoing responses before they go to the client.
In other words, middleware will catch any requests before it actually performs the server-side code for the request. Imagine we have a request to GET some JSON from our Vapor API. Maybe we want to ensure the user is properly authenticated and has permission to GET that information. There is a middleware that will check that for us. We simply have to register middlewares and a lot of the work happens behind the scenes. Depending on the middleware, you may or may not need to do a bit of work later on. There are all kinds of middlewares, but some common ones are: - ErrorMiddleware: It takes any errors thrown when the app is handling an incoming request and automatically turns it into an HTTP response code that can be sent back to the user of the API. - FileMiddleware: This allows you to serve files from the "Public" folder in the project. It's the second blue folder icon in the image above. These files could be anything from images, JSON files, to style sheets. - The Auth
framework has a few more that are used for handling situations described above where you want to make parts (or all) of your API inaccessible without some form of authentication.
routes.swift
The routes
function in this file is where the magic happens. Or at least the part that we're in charge of. Vapor does a lot of the heavy lifting for us, but just as Apple gives us a blank storyboard scene and we are required to build the UI to suit our app's functionality, we need to say what we want to have happen once someone sends a request to the API.
You'll see there's a bit of code in the function already:
public func routes(_ router: Router) throws {
router.get("hello") { req in
return "Hello, world!"
}
}
We have an argument to this function called router: Router
. A Router
is the thing that will handle grabbing requests to a part of the API then allows us to do something based on which part of the API the request goes to and which HTTP method was performed there. In this case, the code is saying "When the user goes to the "/hello" endpoint, return the text "Hello, world!" to them. You can actually test this yourself. If you choose the "Run" scheme (to the left of where you select the device you want to run the app on), then select "My Mac" as the device to run, then run the app, you should be greeted with something like this in the console:
Server starting on http://localhost:8080
In Safari or your browser of choice go to the address that is specified and add "/hello" on to the end. You should see some plain text that says "Hello, world!". And that's the magic of Vapor; with three lines of code you can send something back to the user of the API. Obviously this is a very simple example, but I think it's incredible how easy Vapor makes it to spin up a JSON API.
"Run" group
There is a single file called "main.swift" here. This is the thing that actually begins running the application itself. I've never needed to touch anything in here, and I doubt it's a good idea to anyway unless you really know what you're doing. 😅 If you need to run some code really early in the lifecycle of the app, remember that the "boot.swift" file has you covered there.
"Tests" group
Vapor supports unit testing using the XCTest framework and it's highly recommended to do so, especially for anything that would actually be used in production.
"Dependencies" group
This is where all of the fetched packages live. If you open the disclosure triangle, you can see that there are way more dependencies than are defined in the "Package.swift". Vapor itself has dependencies that it needs to function, so SPM grabbed those as well. I'd not edit these files as any changes would be overwritten when you fetch a new version of the dependency, plus there should be no need to do so in the first place. Vapor seems to be fairly well documented so you can sometimes just look through its source files for help if you're not sure what something is or does.
Wrapping Up
This was a basic introduction into what a Vapor app's file structure looks like. In the next post I'll be exploring the basics of routing and how you can actually run some code when you go to different endpoints in the app. Thanks for reading!